package soendtrs;

import helpers.*;
import soenudp.Command;
import soenudp.UDPHelper;
import soenudp.UDPMessage;
import soenudp.UDPException;
import java.util.Arrays;

/**
 * @author Davis Desormeaux - 5715822
 * 
 * Will manage 'The View' for all groups.
 */
public class DTRSView {
    
    /* Aprox pooling time is PINGWAIT_TIME + WHOLEADS_WAIT.
     * Iteration of a DTRS transaction + BPING response
     * must be below  this amount else 'The View' report the 
     * DTRS as missing. 
     */
    final int PINGWAIT_TIME  = 11000; // Waiting time after a BPING
    final int WHOLEADS_WAIT  =  3000; // Waiting time after a WHO_LEADS
    final int PAUSE_VW_TIME  =  3000; // In case I executed a change (The View)
    final int PASSIVE_ID     =   999; // id that the passive node use.
    boolean   pool_is_on     =  true; // Used to exit the pooling  loop.
    
    /* Multicast Addressing of the groups */
    final String MTL_MULTICAST_ADR   = "235.255.0.1";
    final int    MTL_MULTICAST_PORT  = 9200;
   
    final String TOR_MULTICAST_ADR   = "235.255.0.2";
    final int    TOR_MULTICAST_PORT  = 9100;
    
    /* Unicast Ports of the leaders */
    final int MTL_LEADER_PORT = 20020;
    final int TOR_LEADER_PORT = 20060;
    
    public void closeView() {
        pool_is_on = false;
    }
    
    public void startView() {   
      d.out("Starting View Management\n");
      String MTL_View = "";
      String TOR_View = "";
      UDPHelper groupMTL     = new UDPHelper();
      UDPHelper groupTOR     = new UDPHelper();
      UDPHelper unicstClient = new UDPHelper();
      
      // Start the receiver thread. It will handle the responses (reply).
      DTRSViewRecv receiverThread = new DTRSViewRecv();
      receiverThread.start(); 
    
      try {  
        int previousMTLHash  = 0;
        int previousTORHash  = 0;
        String ipOfMTLLeader = "";
        String ipOfTORLeader = "";
        UDPMessage BPing     = new UDPMessage();
        UDPMessage WHOLeads  = new UDPMessage();

        BPing.command = Command.BPING;
        WHOLeads.command = Command.WHO_LEADS;
        
        groupMTL.joinGroup(MTL_MULTICAST_ADR, MTL_MULTICAST_PORT, true, "THE_VIEW_MTL");
        groupTOR.joinGroup(TOR_MULTICAST_ADR, TOR_MULTICAST_PORT, true, "THE_VIEW_TOR");
        unicstClient.startUnicastClient("THE_VIEW");

        while (pool_is_on) { 
          // Get the IP of the leader by asking the group (the replicas)
          // DTRS leaders (single threaded) don't listen to the group... they only send (broadcast).
          groupMTL.sendToGroup(WHOLeads); // Send a WHOLeads to MTL Group
          groupTOR.sendToGroup(WHOLeads); // Send a WHOLeads To TOR Group

          
          d.sleep(WHOLEADS_WAIT); // Wait for the WHOLeads Reply
          ipOfMTLLeader = getMTLLeader(receiverThread);
          ipOfTORLeader = getTORLeader(receiverThread);

          groupMTL.sendToGroup(BPing);   // Send a BPing to MTL Group
          groupTOR.sendToGroup(BPing);   // Send a BPing to TOR Group
          
          // BPING trou unicast is really just a ping but leaders wont see the difference...          
          BPing.op_text = ipOfTORLeader; // Gives them at the same time other's leader IP Address.
          unicstClient.sendToNode(ipOfMTLLeader, MTL_LEADER_PORT, BPing);  // Send a BPing to MTL Leader
          BPing.op_text = ipOfMTLLeader;
          unicstClient.sendToNode(ipOfTORLeader, TOR_LEADER_PORT, BPing);  // Send a BPing to TOR Leader

          d.sleep(PINGWAIT_TIME); // Give the group time to reply back.
         
          // Thats how I know who's reporting back to my BPING.
          MTL_View = getMTLView(receiverThread);
          TOR_View = getTORView(receiverThread);

          // Create a hash value of 'The View' to check for changes.
          int MTL_Hash = getHashValue(MTL_View);
          int TOR_Hash = getHashValue(TOR_View);
          
          // START Group 'MTL' Faillure detection ----
          // Check if the network as changed by looking at the hash.
          // Report missing on smaller hash value. (When the hash increases someone was added, not removed).
          if (previousMTLHash !=0 &&( previousMTLHash > MTL_Hash )) { 
              d.out("MTL VIEW HASH: "  + MTL_Hash); 
              d.out("MTL VIEW LIST:\n" + MTL_View);  
              d.out("Ooops! Wrong bsd_hash, someone's missing in the MTL Group!");
              beep(4); // Make a beeping sound!
              boolean isLeaderMiss = (MTL_View.indexOf("MTL001") == -1); // View have "MTL001" -> Group have a leader.
              awakeThePassive(groupMTL, isLeaderMiss);
              MTL_Hash = 0; // Reset the hash value to see the new node, so it will be printed on the screen.
              d.sleep(PAUSE_VW_TIME); // Some changes are about to occur. Pause to let them happen.
          } else if ( previousMTLHash !=0 && (previousMTLHash < MTL_Hash) ) { // New Host joinned the network 
              d.out("MTL VIEW HASH: "  + MTL_Hash); 
              d.out("MTL VIEW LIST:\n" + MTL_View);  
              d.out("NEW Host added!\n");            
          } else if (previousMTLHash == 0 &&( previousMTLHash != MTL_Hash )) { // 1st View is printed on the screen.
              d.out("MTL VIEW HASH: "  + MTL_Hash); 
              d.out("MTL VIEW LIST:\n" + MTL_View);  
          }
          // END Group 'TOR' Faillure detection   ----
          
          // START Group 'TOR' Faillure detection ----
          // Check if the network as changed by looking at the hash value.
          // Report missing on smaller hash value. (When the hash increases someone was added, not removed).
          if (previousTORHash !=0 &&( previousTORHash > TOR_Hash )) {
            d.out("TOR VIEW HASH: "  + TOR_Hash); 
            d.out("TOR VIEW LIST:\n" + TOR_View);  
            d.out("Ooops! Wrong bsd_hash, someone's missing in the TOR Group!");
            boolean isLeaderMiss = (MTL_View.indexOf("TOR001") == -1); // View have "TOR001" -> Group have a leader.
            beep(4); // Make a beeping sound!   
            awakeThePassive(groupTOR, isLeaderMiss);
            TOR_Hash = 0; // Reset the hash value to see the new node, so it will be printed on the screen.   
            d.sleep(PAUSE_VW_TIME); // Some changes are about to occur. Pause to let them happen.
          } else if ( previousTORHash !=0 && (previousTORHash < TOR_Hash) ) { // New Host joinned the network 
            d.out("TOR VIEW HASH: "  + TOR_Hash); 
            d.out("TOR VIEW LIST:\n" + TOR_View);  
            d.out("NEW Host added!\n");            
          } else if (previousTORHash == 0 &&( previousTORHash != TOR_Hash )) {// 1st View is printed on the screen.
            d.out("TOR VIEW HASH: "  + TOR_Hash); 
            d.out("TOR VIEW LIST:\n" + TOR_View);  
          }
          // END Group 'TOR' Faillure detection   ----
          
          // Holds the hash value from the previous iteration.
          previousMTLHash = MTL_Hash;
          previousTORHash = TOR_Hash;

          // Start a blank view for next iteration.
          clearAllViews(receiverThread);
        }  
        
        /* Sockets, Queues, and Threads Cleanup */ 
        groupMTL.leaveGroup();
        groupTOR.leaveGroup();
        unicstClient.stopUnicast();
        receiverThread.interrupt(); // Interrupt to make the Blocking Queue return null (to break out while() loop).
      } catch (UDPException e) {
          e.printStackTrace();
      } 
    }
    
    private void awakeThePassive(UDPHelper groupSocket, boolean asLeader) {
      try {  
          UDPMessage wakeUP = new UDPMessage();
          wakeUP.command = Command.WAKEUP;
          if (asLeader) wakeUP.op_text = "LEADER"; 
          else wakeUP.op_text = "REPLICA"; 
          groupSocket.sendToGroup(wakeUP);
        } catch (UDPException e) {
          e.printStackTrace();
        }        
    }
    
    private int getHashValue(String theView) {
        int HashValue = 0;
        String[] VIEW_Sorted = theView.split("\n");
        Arrays.sort(VIEW_Sorted); // Order is important
        for (String s : VIEW_Sorted) HashValue += CustomHash.bsd_hash(s);
        return HashValue;
    }
    
    private String getMTLView(DTRSViewRecv receiverThread) {
        return receiverThread.getMTLView();
    }
    
    private String getTORView(DTRSViewRecv receiverThread) {
        return receiverThread.getTORView();
    }
    
    private String getMTLLeader(DTRSViewRecv receiverThread) {
        return receiverThread.getMTLLeader();
    }
    
    private String getTORLeader(DTRSViewRecv receiverThread) {
        return receiverThread.getTORLeader();
    }   
    
    private void clearAllViews(DTRSViewRecv receiverThread) {
        receiverThread.clearAllViews();
    }
    
    /* Used to make beeping noize when a host is missing */
    private void beep(int beepAmout) {
        for (int i = 0; i < beepAmout; i++ ) {
            java.awt.Toolkit.getDefaultToolkit().beep();
            d.sleep(200);
        }
    }
    
    
    /* Those methods can be used with shutdown.
     * Note, that they are to be performed over
     * unicast.
     */
    // Retrieve the IP Adddress of a NodeID in the View.
    @SuppressWarnings("unused")
    private String getIP(String NodeID, String theView) {
      String[] nodesArr = theView.split("\n");
      for (String nodeRow : nodesArr) {
        String[] nodeRecord = nodeRow.split(":");
        if (nodeRecord[0].equals(NodeID)) return nodeRecord[1];
      }
      return null; // NodeID is not in the View!
    }

    // Retrieve the port of a NodeID in the View.
    @SuppressWarnings("unused")
    private int getPort(String NodeID, String theView) {
      String[] nodesArr = theView.split("\n");
      for (String nodeRow : nodesArr) {
        String[] nodeRecord = nodeRow.split(":");
        if (nodeRecord[0].equals(NodeID)) return Integer.valueOf(nodeRecord[2]);
      }
      return -1; // NodeID is not in the View!
    } 
    
    // ShutDown a Node
    @SuppressWarnings("unused")
    private void shutDown(String IPAdr, int port) {
      try {  
        UDPHelper quick = new UDPHelper();
        quick.startUnicastClient("911");
        UDPMessage shutDown = new UDPMessage();
        shutDown.command = Command.SHUTDOWN;
        quick.sendToNode(IPAdr, port, shutDown);
        quick.stopUnicast();
      } catch (UDPException e) {
        e.printStackTrace();
      }
    }
}
